#==============================================================================
#
#  $Id: tcp.py,v 1.8 2002/02/28 00:02:28 mike Exp $
#
"""
   `TCP/IP` versions of DOPY protocol objects.   
"""
#
#  Copyright (C) 2000, 2001 Michael A. Muller
#
#  Permission is granted to use, modify and redistribute this code,
#  providing that the following conditions are met:
#
#  1) This copyright/licensing notice must remain intact.
#  2) If the code is modified and redistributed, the modifications must 
#  include documentation indicating that the code has been modified.
#  3) The author(s) of this code must be indemnified and held harmless
#  against any damage resulting from the use of this code.
#
#  This code comes with ABSOLUTELY NO WARRANTEE, not even the implied 
#  warrantee of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
#
#==============================================================================

import dopy

import socket, thread, select, string
try:
   from cPickle import Pickler, Unpickler
except ImportError:
   from pickle import Pickler, Unpickler

if 0:
   # debugging code -- GvR
   OrigPickler = Pickler

   class Pickler(OrigPickler):
      def __init__(self, file, bin=0):
         OrigPickler.__init__(self, file, bin)
         self.file = file
         del self.write
      def write(self, s):
         print "write(%s)" % `s`
         self.file.write(s)

   OrigUnpickler = Unpickler

   class Unpickler(OrigUnpickler):
      def __init__(self, file):
         OrigUnpickler.__init__(self, file)
         self.file = file
         del self.readline
         del self.read

      def readline(self):
         s = self.file.readline()
         print "readline() ->", `s`
         return s

      def read(self, n=-1):
         s = self.file.read(n)
         print "read(%d) -> %s" % (n, `s`)
         return s

class Socket:
   
   """
      Provides the FileEventSource interface for sockets.
   """
   
   def fileno(self):
      return self.sock.fileno()
   
   def isActive(self):
      return self.sock is not None
   
   def shutdown(self):
      self.sock = None      


class Protocol(Socket, dopy.Protocol):
   
   def __init__(self, host, port, sock = None):
      """
         parms:
            host::
               [string] dotted ip address of host
            port::
               [int] port number
            sock::
               [socket or None] if provided, this is the socket
               which already has a valid connection.
      """
      hub = dopy.getHub()
      
      self.host = host
      self.port = port
      
      # if a socket was provided, use it.  Otherwise create and connect one.
      if sock:
         self.sock = sock
      else:
         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         self.sock.connect((host, port))
         
      self.rfile = self.sock.makefile('rb')
      self.wfile = self.sock.makefile('wb')
      if hub.isThreaded():
         self.__lk = thread.allocate_lock()
      else:
         self.__lk = None
      hub.addProtocol(self)

   def send(self, obj):   
#      print 'sending %s on tcp:%s:%s:%d' % \
#         (obj, self.host, self.port, self.sock.fileno())
      hub = dopy.getHub()
      if self.__lk:
         self.__lk.acquire()
#         print '   got lock'
      try:
         Pickler(self.wfile).dump(obj)
         self.wfile.flush()
      finally:
         if self.__lk:
            self.__lk.release()
#            print '   released lock'

   def getNext(self):
      # try to unpickle something off of the stream, return None if we
      # terminate.
      try:
#         print 'waiting for request on tcp:%s:%s:%d' % (self.host, self.port,
#                                                        self.sock.fileno()
#                                                        )
                                                        
         # wait till there's an error or something to read
         rdx, wrx, erx = select.select([self.sock], [], [self.sock])
         
         # if there was an error...
         if erx:
            return None
         
         # read the next request
         evt =  Unpickler(self.rfile).load()
         
      except (EOFError, socket.error, IOError):
         return None
#      print 'got %s on tcp:%s:%s:%d' % (evt, self.host, self.port,
#                                        self.sock.fileno()
#                                        )
      return evt

   def getProtocolId(self):
      return 'tcp:%s:%d' % (self.host, self.port)
   
   def __getstate__(self):
      return { 'peer': self.getpeername() }
   
   def __setstate__(self, dict):
      self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
      peer = dict['peer']
      self.sock.connect((peer[0], peer[1]))
      self.rfile = self.sock.makefile('rb')
      self.wfile = self.sock.makefile('wb')

class Server(Socket, dopy.ProtocolServer):
   
   def __init__(self, port):
      self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
      self.sock.bind(('', port))
      self.sock.listen(5)

   def acceptConnection(self):
      sock, addr = self.sock.accept()      
      return Protocol(addr[0], addr[1], sock = sock)

def _factory(id):
   """
      Factory to create TCP protocols.
   """
   type, host, port = id
   port = string.atoi(port)
   return Protocol(host, port)   
   
def makeServer(port):
   hub = dopy.getHub()
   hub.addServer(Server(port))

def remote(host, port, key):
   host = socket.gethostbyname(host)
   hub = dopy.getHub()
   prot = hub.getProtocol('tcp:%s:%d' % (host, port))
   return dopy.DynamicRemoteObject(prot, key)

# register the factory
dopy.protocolFactory.register('tcp', _factory)
